﻿//--------------------------------------------------------------------------
// 
//  Copyright (c) Chili Software.  All rights reserved. 
// 
//  File: LinqQueryCommandBuilder.cs
//
//  Description: Class that converts LinqQueries to CompiledQuery.
// 
//--------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Data;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using System.Text;
using Chili.Opf3.Mapping;
using Chili.Opf3.Storages;
using CompiledQuery = Chili.Opf3.Query.CompiledQuery;
using IQuery = Chili.Opf3.Query.IQuery;
using QueryTypes = Chili.Opf3.Query.QueryTypes;
using System.Reflection;

namespace Chili.Opf3.Linq
{
	/// <summary>
	/// This class converts <see cref="LinqQuery{T}">LinqQuery</see> classes to storage
	/// dependent <see cref="Opf3.Query.CompiledQuery">CompiledQuery</see> classes.
	/// </summary>
	public sealed class LinqQueryCommandBuilder : IStorageCommandBuilder
	{
		#region SqlBuilder

		/// <summary>
		/// This class holds the strings and parameters used to create the 
		/// final SQL statement.
		/// </summary>
		internal sealed class SqlBuilder
		{
			private SqlStorageBase _storage;
			private List<string> _entities = new List<string>();
			private List<string> _joinedEntities = new List<string>();
			private List<IDataParameter> _parameters = new List<IDataParameter>();
			private StringBuilder _orderby = null;
			private StringBuilder _where = null;
			private StringBuilder _groupJoin = null;
			private StringBuilder _join = null;
			private StringBuilder _select = null;
			private ExpressionsBag _bag = null;
			private Type _persistentType = null;
			private QueryTypes _queryType = QueryTypes.Unknown;

			/// <summary>
			/// Creates a new instance of the <see cref="SqlBuilder">SqlBuilder Class</see>.
			/// </summary>
			/// <param name="storage">The storage on which the SQL runs.</param>
			/// <param name="bag">The expressions bag that is used to compile the SQL.</param>
			/// <param name="persistentType">The type of the persistent object.</param>
			/// <param name="queryType">The type of query that is about to be constructed.</param>
			public SqlBuilder(SqlStorageBase storage, ExpressionsBag bag, Type persistentType, QueryTypes queryType)
			{
				if (storage == null)
					throw new ArgumentNullException("storage");
				if (bag == null)
					throw new ArgumentNullException("bag");
				if (persistentType == null)
					throw new ArgumentNullException("persistentType");

				_persistentType = persistentType;
				_storage = storage;
				_bag = bag;
				_queryType = queryType;
			}

			/// <summary>
			/// Gets the group join part of the SQL.
			/// </summary>
			public StringBuilder GroupJoin
			{
				get { return _groupJoin ?? (_groupJoin = new StringBuilder()); }
			}

			/// <summary>
			/// Gets the order by part of the SQL.
			/// </summary>
			public StringBuilder OrderBy
			{
				get { return _orderby ?? (_orderby = new StringBuilder()); }
			}

			/// <summary>
			/// Gets the where part of the SQL.
			/// </summary>
			public StringBuilder Where
			{
				get { return _where ?? (_where = new StringBuilder()); }
			}

			/// <summary>
			/// Gets the join part of the SQL.
			/// </summary>
			public StringBuilder Join
			{
				get { return _join ?? (_join = new StringBuilder()); }
			}

			/// <summary>
			/// Gets the select part of the SQL.
			/// </summary>
			public StringBuilder Select
			{
				get { return _select ?? (_select = new StringBuilder()); }
			}

			/// <summary>
			/// Gets the expressions bag associated with this class.
			/// </summary>
			public ExpressionsBag ExpressionsBag
			{
				get { return _bag; }
			}

			/// <summary>
			/// Gets the persistent type.
			/// </summary>
			public Type PersistentType
			{
				get { return _persistentType; }
			}

			/// <summary>
			/// Gets the <see cref="SqlStorageBase">storage</see> where the query is going
			/// to run on.
			/// </summary>
			public SqlStorageBase Storage
			{
				get { return _storage; }
			}

			/// <summary>
			/// Adds an entity to the list of entities.
			/// </summary>
			public void AddEntity(string name)
			{
				if (name == null)
					throw new ArgumentNullException("name");

				if (this.JoinedEntityIsAdded)
					_joinedEntities.Add(name);
				else
				{
					if (!_entities.Contains(name) && !_joinedEntities.Contains(name))
						_entities.Add(name);
				}
			}

			/// <summary>
			/// True to ignore all entities that are added.
			/// </summary>
			public bool JoinedEntityIsAdded { get; set; }

			/// <summary>
			/// Gets the last entity that was used in a join.
			/// </summary>
			/// <returns></returns>
			public string GetLastJoinEntity()
			{
				return _joinedEntities[_joinedEntities.Count - 1];
			}

			/// <summary>
			/// Gets and sets whether we are currently in a left join process.
			/// </summary>
			public bool InLeftJoin { get; set; }

			/// <summary>
			/// Gets a string that holds all the entities used in the query.
			/// </summary>
			/// <returns></returns>
			private string GetEntitiesString()
			{
				// If we got no entity use the persistent type to fetch the entity.
				if (_entities.Count == 0)
				{
					TypeMapping mapping = TypeMapping.GetTypeMapping(this.PersistentType);
					return _storage.GetValidEntityName(mapping.PersistentAttribute.Entity);
				}

				// Otherwise run over the entities and create the entities string.
				StringBuilder result = new StringBuilder();
				foreach(string e in _entities)
				{
					if (result.Length > 0)
						result.Append(", ");

					result.Append(_storage.GetValidEntityName(e));
				}

				return result.ToString();
			}

			/// <summary>
			/// Gets the select string for the query.
			/// </summary>
			/// <returns></returns>
			private string GetSelectString(Type type)
			{
				if (type == null)
					throw new ArgumentNullException("type");

				string entity = TypeMapping.GetTypeMapping(type).PersistentAttribute.Entity;
				return _storage.GetValidEntityName(entity) + ".*";
			}

			/// <summary>
			/// Gets the list of parameters.
			/// </summary>
			public List<IDataParameter> Parameters
			{
				get { return _parameters; }
			}

			/// <summary>
			/// Returns a <see cref="CompiledQuery">CompiledQuery</see> instance that has been
			/// generated by this instance.
			/// </summary>
			/// <returns></returns>
			public CompiledQuery ToCompiledQuery()
			{
				// Compile the command text.
				StringBuilder commandText = new StringBuilder();

//				commandText.AppendLine("-- Create by Opf3 LINQ provider build: " + typeof(ObjectContext).Assembly.GetName().Version);

				if (_queryType == QueryTypes.Load)
				{
					commandText.Append("SELECT ");
					// add the top count if required.
					if (_bag.TopCount > 0)
						commandText.Append("TOP " + _bag.TopCount + " ");

					commandText.Append((_select != null && _select.Length > 0 ? _select.ToString() :
						this.GetSelectString(this.PersistentType)) + " FROM ");
				}
				else if (_queryType == QueryTypes.Delete)
				{
					commandText.Append("DELETE FROM ");
				}
				else
					throw new NotSupportedException("Only load and delete queries are supported.");

				commandText.Append(this.GetEntitiesString());

				if (_join != null && _join.Length > 0)
					commandText.Append(" " + _join.ToString());

				if (_groupJoin != null && _groupJoin.Length > 0)
					commandText.Append(" " + _groupJoin.ToString());

				if (_where != null && _where.Length > 0) 
					commandText.Append(" WHERE " + _where.ToString());

				if (_orderby != null && _orderby.Length > 0) 
					commandText.Append(" ORDER BY " + _orderby.ToString());

				// Create the storage command and add the command text.
				IDbCommand command = _storage.CreateCommand();
				command.CommandText = commandText.ToString();

				// Add the parameters to the command..
				foreach (IDataParameter parameter in this.Parameters)
				{
					command.Parameters.Add(parameter);
				}

				// Return the CompiledQuery.
				return new CompiledQuery(command, QueryTypes.Load);
			}
		}

		#endregion

		private static List<IMethodHandler> _handlers = new List<IMethodHandler>();

		#region IStorageCommandBuilder Members

		/// <summary>
		/// Converts the <see cref="Opf3.Query.ObjectExpression">ObjectExpression</see> to
		/// a storage dependent <see cref="Opf3.Query.CompiledQuery">CompiledQuery</see>. This method should only be
		/// called by a class that implements the <see cref="IQuery">IQuery</see> interface.
		/// </summary>
		/// <param name="query"><see cref="Opf3.Query.IQuery">Query</see> object
		/// that is converted to a storage dependent <see cref="Opf3.Query.CompiledQuery">CompiledQuery</see>.
		/// </param>
		/// <param name="storage">The storage that the query is compiled for.</param>
		/// <returns>A storage dependent <see cref="Opf3.Query.CompiledQuery">CompiledQuery</see>.</returns>
		public CompiledQuery ToCompiledQuery(IStorage storage, IQuery query)
		{
			if (storage == null)
				throw new ArgumentNullException("storage");
			if (query == null)
				throw new ArgumentNullException("query");
			// Check if the storage is inheriting from SQL storage base.
			SqlStorageBase ssb = storage as SqlStorageBase;
			if (ssb == null)
				throw new NotSupportedException("Only classes that inherit from SqlStorageBase are supported by this class.");
			ILinqQuery lq = query as ILinqQuery;
			if (lq == null)
				throw new NotSupportedException("Only LinqQuery allowed in this class.");

			return CreateQuery(lq.Expressions, ssb, lq.PersistentType, QueryTypes.Load);
		}

		/// <summary>
		/// Returns whether the <see cref="IStorageCommandBuilder">IStorageCommandBuilder</see> supports 
		/// the query type on the given storage.
		/// </summary>
		/// <param name="storage">The storage for which the query should be compiled.</param>
		/// <param name="query">The query that should be compiled to a storage dependent command.</param>
		/// <returns>True if the query can be parsed by the <see cref="IStorageCommandBuilder">IStorageCommandBuilder</see>.</returns>
		public bool IsSupported(IStorage storage, IQuery query)
		{
			return (storage is SqlStorageBase && (query is ILinqQuery));
		}

		#endregion

		/// <summary>
		/// Creates a delete query from a <see cref="LinqQuery{T}">LinqQuery</see>.
		/// </summary>
		/// <param name="query"><see cref="Opf3.Query.IQuery">Query</see> object
		/// that is converted to a storage dependent <see cref="Opf3.Query.CompiledQuery">CompiledQuery</see>.
		/// </param>
		/// <param name="storage">The storage that the query is compiled for.</param>
		/// <returns>A storage dependent <see cref="Opf3.Query.CompiledQuery">CompiledQuery</see>.</returns>
		internal static CompiledQuery ToCompiledDeleteQuery(IStorage storage, ILinqQuery query)
		{
			// Check if the storage is inheriting from SQL storage base.
			SqlStorageBase ssb = storage as SqlStorageBase;
			if (ssb == null)
				throw new NotSupportedException("Only classes that inherit from SqlStorageBase are supported by this class.");

			return CreateQuery(query.Expressions, ssb, query.PersistentType, QueryTypes.Delete);
		}

		/// <summary>
		/// Static constructor.
		/// </summary>
		static LinqQueryCommandBuilder()
		{
			_handlers.Add(new QueryUtilityMethodsHandler());
			_handlers.Add(new StringMethodsHandler());
		}

		/// <summary>
		/// Gets the list of registered method handlers for utility classes registered with the command builder.
		/// </summary>
		public static List<IMethodHandler> MethodHandlers
		{
			get { return _handlers; }
		}
		
		/// <summary>
		/// Creates the query from the given <see cref="ILinqQuery">ILinqQuery</see>.
		/// </summary>
		/// <param name="bag">The expressions bag used to create the output.</param>
		/// <param name="storage">The <see cref="SqlStorageBase">SqlStorageBase</see> used to create the SQL.</param>
		/// <param name="persistentType">The type of the persistent object.</param>
		/// <param name="queryType">The type of the current query that is to execute.</param>
		private static CompiledQuery CreateQuery(ExpressionsBag bag, SqlStorageBase storage, Type persistentType,
			QueryTypes queryType)
		{
			// Check if the expressions bag is empty. That means the query is empty.
			if (bag.IsEmpty())
			{
				TypeMapping mapping = TypeMapping.GetTypeMapping(persistentType);

				// We create a default query in that scenario.
				IDbCommand command = storage.CreateCommand();
				command.CommandText = "select * from " + storage.GetValidEntityName(mapping.PersistentAttribute.Entity);
				return new CompiledQuery(command, QueryTypes.Load);
			}
			
			SqlBuilder builder = new SqlBuilder(storage, bag, persistentType, queryType);

			// Process the join part.
			foreach (Expression expr in bag.Join)
			{
				builder.Join.Append(ProcessExpression(expr, builder, new StringBuilder()));
			}

			// Process the group join part.
			foreach (Expression expr in bag.GroupJoin)
			{
				if (bag.SelectMany != null)
				{
					MethodCallExpression express = bag.SelectMany as MethodCallExpression;
					if (express != null)
					{
						var result = ProcessExpression(express.Arguments[1], builder, new StringBuilder()).ToString();
						// signalize that we have to process a left join.
						if (result == "DefaultIfEmpty")
							builder.InLeftJoin = true;

						builder.GroupJoin.Append(ProcessExpression(expr, builder, new StringBuilder()));
						builder.InLeftJoin = false;
						continue;
					}
				}
//				else
//				{
//					if (builder.GroupJoin.Length > 0) 
//						builder.GroupJoin.Append(" and ");
//					builder.GroupJoin.Append(ProcessExpression(expr, builder, new StringBuilder()));
//				}

				throw new NotSupportedException("GroupJoin of the given kind is not supported!");
			}

			// Process the where part.
			foreach (Expression expr in bag.Where)
			{
				if (builder.Where.Length > 0)
					builder.Where.Append(" and ");
				builder.Where.Append(ProcessExpression(expr, builder, new StringBuilder()));
			}

			// Process the order by part.
			foreach (OrderByExpression expr in bag.OrderBy)
			{
				if (builder.OrderBy.Length > 0)
					builder.OrderBy.Append(", ");
				builder.OrderBy.Append(ProcessExpression(expr.Expression, builder, new StringBuilder()));
				builder.OrderBy.Append(expr.Direction == Chili.Opf3.Query.SortDirections.Ascending ? " asc" : " desc");
			}

			// Process the select part.
			if (TypeMapping.GetTypeMapping(persistentType).IsAnonymousType && bag.Select != null)
				builder.Select.Append(ProcessExpression(bag.Select, builder, new StringBuilder()));

			// Check if we have to process the select many to get the fields.
			if (builder.Select.Length == 0)
			{
				MethodCallExpression express = bag.SelectMany as MethodCallExpression;
				if (express != null && express.Arguments[2] is UnaryExpression)
					builder.Select.Append(ProcessExpression(express.Arguments[2], builder, new StringBuilder()));
			}

			// Return the compiled query.
			return builder.ToCompiledQuery();
		}

        /// <summary>
        /// Processes the given expression.
        /// </summary>
        /// <param name="expression">The expression that is to process.</param>
        /// <param name="builder">The builder holding the final query.</param>
        /// <param name="value">The value feed into the current method.</param>
        internal static StringBuilder ProcessExpression(Expression expression, SqlBuilder builder, StringBuilder value)
        {
            return ProcessExpression(expression, builder, value, null);
        }

		/// <summary>
		/// Processes the given expression.
		/// </summary>
		/// <param name="expression">The expression that is to process.</param>
		/// <param name="builder">The builder holding the final query.</param>
		/// <param name="value">The value feed into the current method.</param>
        /// <param name="userDefinedType">The UserDefinedType that will be used to convert to a simple data type value.</param>
		internal static StringBuilder ProcessExpression(Expression expression, SqlBuilder builder, StringBuilder value, IQueryableUserDefinedType userDefinedType)
		{
			// Select and process the type of expression.
			MethodCallExpression mce = expression as MethodCallExpression;
			if (mce != null)
				return ProcessMethodCallExpression(mce, builder, value);
			
			ConstantExpression ce = expression as ConstantExpression;
            if (ce != null)
                return AddParameter(ce.Value, expression.Type, builder, value, userDefinedType);

			UnaryExpression ue = expression as UnaryExpression;
			if (ue != null)
				return ProcessUnaryExpression(ue, builder, value);

			LambdaExpression le = expression as LambdaExpression;
			if (le != null)
				return ProcessExpression(le.Body, builder, value);

			BinaryExpression be = expression as BinaryExpression;
			if (be != null)
				return ProcessBinaryExpression(be, builder, value);

			MemberExpression me = expression as MemberExpression;
			if (me != null)
				return ProcessMemberExpression(me, builder, value, userDefinedType);

			NewExpression ne = expression as NewExpression;
			if (ne != null)
				return ProcessNewExpression(ne, builder, value);

			if (expression is ParameterExpression)
				return value;

			NewArrayExpression nae = expression as NewArrayExpression;
			if (nae != null)
				return ProcessNewArrayExpression(nae, builder, value);

			// Not all expressions might be supported. If one is not supported and exception is thrown.
			throw new NotSupportedException("Expression type: " + expression.GetType() + " not supported.");
		}

		/// <summary>
		/// Processes a new array expression.
		/// </summary>
		/// <param name="expression">The expression that is to process.</param>
		/// <param name="builder">The builder holding the final query.</param>
		/// <param name="value">The value feed into the current method.</param>
		private static StringBuilder ProcessNewArrayExpression(NewArrayExpression expression, SqlBuilder builder, StringBuilder value)
		{
			value.Append("(");

			// Loop over all items and add them to the builder.
			for(int i = 0; i < expression.Expressions.Count; i++)
			{
				if (i > 0) value.Append(", ");
				ProcessExpression(expression.Expressions[i], builder, value);
			}

			return value.Append(")");
		}

		/// <summary>
		/// Processes an unary expression.
		/// </summary>
		/// <param name="expression">The expression that is to process.</param>
		/// <param name="builder">The builder holding the final query.</param>
		/// <param name="value">The value feed into the current method.</param>
		private static StringBuilder ProcessUnaryExpression(UnaryExpression expression, SqlBuilder builder, StringBuilder value)
		{
			// Check if we have a prefix not. In that case we need to handle it.
			if (expression.NodeType == ExpressionType.Not)
			{
				value.Append("not (");
				ProcessExpression(expression.Operand, builder, value);
				value.Append(")");
			}
			else
			{
				ProcessExpression(expression.Operand, builder, value);
			}

			return value;
		}

		/// <summary>
		/// Processes a new expression.
		/// </summary>
		/// <param name="expression">The expression that is to process.</param>
		/// <param name="builder">The builder holding the final query.</param>
		/// <param name="value">The value feed into the current method.</param>
		private static StringBuilder ProcessNewExpression(NewExpression expression, SqlBuilder builder, StringBuilder value)
		{
			// Loop over all arguments to compile the fields that are returned.
			for (int i = 0; i < expression.Arguments.Count; i++)
			{
				if (i > 0) value.Append(", ");

				MethodCallExpression mc = expression.Arguments[i] as MethodCallExpression;
				if (mc != null)
				{
					// Process the method call expression.
					ProcessMethodCallExpression(mc, builder, value);
				}
				else
				{
					// Check if the expression is a member expression. If not return immediately with no result!
					MemberExpression me = expression.Arguments[i] as MemberExpression;
					if (me != null)
					{
						// Process the member expression.
						ProcessMemberExpression(me, builder, value, null);
					}
					else
					{
						// check if the expression is a binary expression.
						BinaryExpression be = expression.Arguments[i] as BinaryExpression;
						if (be != null)
						{
							// process the binary expression.
							ProcessBinaryExpression(be, builder, value);
						}
						else
						{
							// if we reached this point we return null. This is required because with 
							// joins internally two anonymous types are generated. One that takes both persistents
							// of the join and one that does the select on these items. With the one that
							// takes the two persistents we end up here. We don't want to process that one and 
							// return null therefore.
							return null;
						}
					}
				}
				
				// HACK: Don't know why that is not giving the property.
				string alias = expression.Members[i].Name.Replace("get_", string.Empty).Replace("(", string.Empty).Replace(")", string.Empty);
				value.Append(" as " + alias);
			}

			return value;
		}

        /// <summary>
        /// Get the inner instance of a nested expression.
        /// </summary>
        /// <param name="expression">The expression to get the inner instance from.</param>
        private static object GetInnerInstance(MemberExpression expression)
        {
            // if the inner expression is no constant expression we leave
            ConstantExpression innerExpr = expression.Expression as ConstantExpression;
            if (innerExpr == null)
                return null;

            object innerInstance = innerExpr != null ? innerExpr.Value : null;

            PropertyInfo prop = expression.Member as PropertyInfo;
            if (prop != null)
                return prop.GetValue(innerInstance, null);
            FieldInfo field = expression.Member as FieldInfo;
            if (field != null)
                return field.GetValue(innerInstance);

            return null;
        }

		/// <summary>
		/// Processes a member expression.
		/// </summary>
		/// <param name="expression">The expression that is to process.</param>
		/// <param name="builder">The builder holding the final query.</param>
		/// <param name="value">The value feed into the current method.</param>
        private static StringBuilder ProcessMemberExpression(MemberExpression expression, SqlBuilder builder, StringBuilder value, IQueryableUserDefinedType userDefinedType)
		{
			// check if the given type has a type mapping defined.
			Type type = expression.Expression.Type;
            if (!TypeMapping.HasPersistentMapping(type))
            {
                ConstantExpression expr = expression.Expression as ConstantExpression;

                object instance = null;

                // if the inner expression is no constant expression it might be an nested memberexpression.
                if (expr == null)
                {
                    MemberExpression exprMember = expression.Expression as MemberExpression;

                    // if the inner expression is no member expression we leave
                    if (exprMember == null)
                        return value;

                    // if it has a type mapping defined then process the member expression
                    if (TypeMapping.HasPersistentMapping(exprMember.Expression.Type))
                        return ProcessMemberExpression(exprMember, builder, value, userDefinedType);
                    else
                        instance = GetInnerInstance(exprMember);
                }
                else
                {
                    // check if we got an instance that we can use to invoke.
                    instance = expression.Expression != null ? expr.Value : null;
                }

                if (instance == null)
                    return value;

                PropertyInfo prop = expression.Member as PropertyInfo;
                if (prop != null)
                    return AddParameter(prop.GetValue(instance, null), expression.Type, builder, value, userDefinedType);
                FieldInfo field = expression.Member as FieldInfo;
                if (field != null)
                    return AddParameter(field.GetValue(instance), expression.Type, builder, value, userDefinedType);

                throw new NotSupportedException("Other types than methods, fields and properties can't be evaluated by the builder.");

            }

			// Get the type mapping for the associated member type.
			TypeMapping mapping = null;
			// If we are dealing with an anonymous type we are outa here!
			if ((mapping = TypeMapping.GetTypeMapping(expression.Expression.Type)).IsAnonymousType)
				return value;

			// Add the entity to the builders entities.
			builder.AddEntity(mapping.PersistentAttribute.Entity);

			// Get the MemberInfo object.
			IMemberInfo member = mapping.Members.GetMemberInfoByName(expression.Member.Name);
			if (member == null)
				throw new InvalidOperationException("Member " + expression.Member.Name + "not found in type " + expression.Member.DeclaringType);

			// Append the value to the string builder.
			return value.AppendFormat("{0}.{1}", builder.Storage.GetValidEntityName(mapping.PersistentAttribute.Entity), 
				builder.Storage.GetValidFieldName(member.FieldAttribute.FieldName));
		}

        /// <summary>
        /// Creates and returns a UserDefinedType from an expression.
        /// </summary>
        /// <param name="expression">The expression to resolve the UserDefinedType from</param>
        /// <returns>The resolved UserDefinedType</returns>
        private static IQueryableUserDefinedType ResolveUserDefinedType(Expression expression)
        {
            MemberExpression me = (expression as MemberExpression);
            if (me == null)
                return null;

            MemberExpression memberExpression = me.Expression as MemberExpression;
            if (memberExpression == null)
                return null;

            if (typeof(IQueryableUserDefinedType).IsAssignableFrom(memberExpression.Type))
            {
                return (IQueryableUserDefinedType)Activator.CreateInstance(memberExpression.Type);
            }

            return null;
        }

		/// <summary>
		/// Processes a binary expression.
		/// </summary>
		/// <param name="expression">The expression that is to process.</param>
		/// <param name="builder">The builder holding the final query.</param>
		/// <param name="value">The value feed into the current method.</param>
		private static StringBuilder ProcessBinaryExpression(BinaryExpression expression, SqlBuilder builder, StringBuilder value)
		{
			// All binary expressions are put in brackets.
			value.Append("(");

/*			
			ConstantExpression ce = expression.Right as ConstantExpression;
			if (ce != null && ce.Value == null)
			{
				// Process the left expression, then the node type and then the right expression.
				ProcessExpression(expression.Left, builder, value);

				if (expression.NodeType == ExpressionType.Equal)
					value.Append(" is null");
				else if (expression.NodeType == ExpressionType.NotEqual)
					value.Append(" is not null");
				else
					throw new NotSupportedException("Only equal or non equal are supported with null parameters.");
			}
			else 
*/
			if (expression.NodeType == ExpressionType.ExclusiveOr)
			{
				// Not supported by SQL. We need to build it on our own: (a && !b) || (!a && b)
				value.Append("(");
				value.Append(ProcessExpression(expression.Left, builder, new StringBuilder()).ToString());
				value.Append(" and not ");
				value.Append(ProcessExpression(expression.Right, builder, new StringBuilder()).ToString());
				value.Append(") or (not ");
				value.Append(ProcessExpression(expression.Left, builder, new StringBuilder()).ToString());
				value.Append(" and ");
				value.Append(ProcessExpression(expression.Right, builder, new StringBuilder()).ToString());
				value.Append(")");
			}
			else
			{
                // Process the left expression, then the node type and then the right expression.

                // left side
                IQueryableUserDefinedType userDefinedType = ResolveUserDefinedType(expression.Right);
                ProcessExpression(expression.Left, builder, value);

                // node type
				ProcessNodeType(expression.NodeType, builder, value);

                // right side
                userDefinedType = ResolveUserDefinedType(expression.Left);
                ProcessExpression(expression.Right, builder, value, userDefinedType);
			}

			// Add the closing parentesis.
			return value.Append(")");
		}

		/// <summary>
		/// Processes the given node type.
		/// </summary>
		/// <param name="expressionType">The expression type (node type) that is processed.</param>
		/// <param name="builder">The builder holding the final query.</param>
		/// <param name="value">The value feed into the current method.</param>
		private static StringBuilder ProcessNodeType(ExpressionType expressionType, SqlBuilder builder, StringBuilder value)
		{
			switch (expressionType)
			{
				case ExpressionType.Equal:
					value.Append(" = ");
					break;
				case ExpressionType.GreaterThan:
					value.Append(" > ");
					break;
				case ExpressionType.GreaterThanOrEqual:
					value.Append(" >= ");
					break;
				case ExpressionType.And:
				case ExpressionType.AndAlso:
					value.Append(" and ");
					break;
				case ExpressionType.LessThan:
					value.Append(" < ");
					break;
				case ExpressionType.LessThanOrEqual:
					value.Append(" <= ");
					break;
				case ExpressionType.Or:
				case ExpressionType.OrElse:
					value.Append(" or ");
					break;
				case ExpressionType.NotEqual:
					value.Append(" <> ");
					break;
				case ExpressionType.Add:
					value.Append(" + ");
					break;
				case ExpressionType.Divide:
					value.Append(" / ");
					break;
				case ExpressionType.Modulo:
					value.Append(" % ");
					break;
				case ExpressionType.Multiply:
					value.Append(" * ");
					break;
				case ExpressionType.Negate:
				case ExpressionType.Not:
					value.Append(" not ");
					break;
				case ExpressionType.Subtract:
					value.Append(" - ");
					break;
				default:
					throw new NotSupportedException("ExpressionType: " + expressionType.ToString() + " not supported.");
			}

			return value;
		}

		/// <summary>
		/// Processes a method call expression.
		/// </summary>
		/// <param name="expression">The expression that is to process.</param>
		/// <param name="builder">The builder holding the final query.</param>
		/// <param name="value">The value feed into the current method.</param>
		private static StringBuilder ProcessMethodCallExpression(MethodCallExpression expression, SqlBuilder builder, StringBuilder value)
		{
			// Check if there is a handler registerd for the method, who would handle this method for us.
			for (int i = 0; i < _handlers.Count; i++)
			{
				// Get the handler and invoke it.
				IMethodHandler handler = _handlers[i];
				if (handler.CanHandle(expression.Method, expression.Method.DeclaringType))
					return value.Append(handler.Handle(expression, new MethodHandlerArgs(builder)));
			}

			if (expression.Arguments.Count == 0)
			{
				// if there has been no argument given we invoke the method and return the result.
				object instance = ((ConstantExpression)expression.Object).Value;
				return AddParameter(expression.Method.Invoke(instance, null), expression.Type, builder, value, null);
			}
			else if (expression.Arguments.Count == 1)
			{
				return value.Append(expression.Method.Name);
			}
			else if (expression.Arguments.Count == 2)
			{
				// If there are two arguments only the second one need to be handled.
				return ProcessExpression(expression.Arguments[1], builder, value);
			}
			else if (expression.Arguments.Count == 5)
			{
				StringBuilder tempValue = new StringBuilder();

				// If there are five arguments it is a join.
				ProcessExpression(expression.Arguments[2], builder, tempValue);
				tempValue.Append(" = ");

				// make sure that entities that are found in the second part of the join
				// are not added to the builder.
				builder.JoinedEntityIsAdded = true;
				ProcessExpression(expression.Arguments[3], builder, tempValue);
				builder.JoinedEntityIsAdded = false;

				// get the entity that was used in the join.
				string entity = builder.Storage.GetValidEntityName(builder.GetLastJoinEntity());

				if (builder.InLeftJoin)
					value.AppendFormat("LEFT OUTER JOIN {0} ON {1} ", entity, tempValue.ToString());
				else
					value.AppendFormat("INNER JOIN {0} ON {1} ", entity, tempValue.ToString());

				if (builder.Select.Length == 0)
				{
					// Check if we have to compile the fields that have been selected from the join.
					var result = ProcessExpression(expression.Arguments[4], builder, new StringBuilder());
					if (result != null)
						builder.Select.Append(result.ToString());
				}
			}

			return value;
		}

		/// <summary>
		/// Adds the parameter to the list of parameters.
		/// </summary>
		/// <param name="paramValue">The value of parameters.</param>
		/// <param name="type">The type of the value.</param>
		/// <param name="builder">The builder holding the final query.</param>
		/// <param name="value">The value feed into the current method.</param>
		internal static StringBuilder AddParameter(object paramValue, Type type, SqlBuilder builder, StringBuilder value, IQueryableUserDefinedType userDefinedType)
		{
			// Set the param value to DB null if the param value is null.
			if (paramValue == null)
			{
				// check if the last comparison is an equals.
				if (value[value.Length - 2] == '=')
				{
					// remove the last two characters and add the null comparison instead.
					value.Remove(value.Length - 3, 3);
					value.Append(" is null");
				}
				else
				{
					value.Remove(value.Length - 4, 4);
					value.Append(" is not null");
				}

				return value;
			}
			else
			{
                // if it's a userDefinedType then convert to simple type
                if (userDefinedType != null)
                {
                    userDefinedType.Load(paramValue);
                    paramValue = userDefinedType.ToSimpleDataType();
                }
                
                // Add the parameter.
				string name = "p" + builder.Parameters.Count;
				builder.Parameters.Add(builder.Storage.CreateParameter(builder.Parameters.Count, ref name, paramValue, type));

				return value.Append(name);
			}
		}
	}
}
